import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@common/utils/app.utils';
import { VulnerabilitiesFilterService } from '../vulnerabilities.filter.service';
import { i18nPdfTranslateService } from './i18n-pdf-transalte.service';
import { MapConstant } from '@common/constants/map.constant';
import { DatePipe } from '@angular/common';
import { saveAs } from 'file-saver';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';

(<any>pdfMake).vfs = pdfFonts.pdfMake.vfs;

@Injectable()
export class VulnerabilityViewPdfService {
  private pdf;
  private worker;
  private progressSubject$ = new BehaviorSubject<number>(0);
  progress$ = this.progressSubject$.asObservable();

  constructor(
    private i18nPdfTranslateService: i18nPdfTranslateService,
    private translateService: TranslateService,
    private utilsService: UtilsService,
    private vulnerabilitiesFilterService: VulnerabilitiesFilterService,
    private datePipe: DatePipe
  ) {}

  runWorker() {
    this.progressSubject$.next(0);
    const vulnerabilitiesList = this.vulnerabilitiesFilterService.filteredCis;
    this.pdf = null;
    if (this.worker) {
      this.worker.terminate();
      console.info('killed an existing running worker...');
    }
    this.createWorker();
    if (this.worker) {
      if (vulnerabilitiesList) {
        let vulnerabilities4Pdf = vulnerabilitiesList.map(vulnerability => {
          vulnerability.workloads = vulnerability.workloads.filter(workload =>
            this.vulnerabilitiesFilterService.namespaceFilter(workload)
          );
          return vulnerability;
        });
        let docData = Object.assign(
          {},
          {
            data:
              vulnerabilities4Pdf.length >= MapConstant.REPORT_TABLE_ROW_LIMIT
                ? vulnerabilities4Pdf.slice(
                    0,
                    MapConstant.REPORT_TABLE_ROW_LIMIT
                  )
                : vulnerabilities4Pdf,
          },
          {
            metadata: this.i18nPdfTranslateService.getI18NMessages({
              filteredCount: vulnerabilitiesList.length,
            }),
          },
          { images: MapConstant.imageMap }, //Picture URI code which is used in PDF
          { charts: this.getChartsForPDF() },
          { rowLimit: MapConstant.REPORT_TABLE_ROW_LIMIT }
        );
        console.log('Post message to worker...');
        this.worker.postMessage(JSON.stringify(this._formatContent(docData)));
      } else {
        console.warn('no data in audit.');
      }
      this.worker.onmessage = event => {
        this.pdf = event.data.blob;
        this.progressSubject$.next(Math.floor(event.data.progress * 100));
      };
    } else {
      this.progressSubject$.next(100);
    }
  }

  createWorker() {
    if (typeof Worker !== 'undefined') {
      this.worker = new Worker(
        new URL('./vulnerability-view-pdf.worker.ts', import.meta.url)
      );
    }
  }

  downloadPdf() {
    const vulnerabilitiesList = this.vulnerabilitiesFilterService.filteredCis;
    if (this.worker && this.pdf) {
      saveAs(
        this.pdf,
        `${this.translateService.instant(
          'scan.report.TITLE'
        )}_${this.utilsService.parseDatetimeStr(new Date())}.pdf`
      );
    } else {
      let vulnerabilities4Pdf = vulnerabilitiesList.map(compliance => {
        compliance.workloads = compliance.workloads.filter(workload =>
          this.vulnerabilitiesFilterService.namespaceFilter(workload)
        );
        return compliance;
      });
      let docData = Object.assign(
        {},
        {
          data:
            vulnerabilities4Pdf.length >= MapConstant.REPORT_TABLE_ROW_LIMIT
              ? vulnerabilities4Pdf.slice(0, MapConstant.REPORT_TABLE_ROW_LIMIT)
              : vulnerabilities4Pdf,
        },
        {
          metadata: this.i18nPdfTranslateService.getI18NMessages({
            filteredCount: vulnerabilitiesList.length,
          }),
        },
        { images: MapConstant.imageMap }, //Picture URI code which is used in PDF
        { charts: this.getChartsForPDF() },
        { rowLimit: MapConstant.REPORT_TABLE_ROW_LIMIT }
      );
      let docDefinition = this._formatContent(docData);
      this.drawReport(docDefinition);
    }
  }

  _formatContent(docData) {
    let metadata = docData.metadata;
    let images = docData.images;
    let charts = docData.charts;

    let docDefinition: any = {
      info: {
        title: metadata.title,
        author: 'NeuVector',
        subject: 'Vulnerability report',
        keywords: 'Vulnerability report',
      },
      headerData: {
        text: metadata.others.headerText,
        alignment: 'center',
        italics: true,
        style: 'pageHeader',
      },
      footerData: {
        line: {
          image: images.FOOTER_LINE,
          width: 650,
          height: 1,
          margin: [50, 5, 0, 10],
        },
        text: metadata.others.footerText,
      },
      header: function (currentPage) {
        if (currentPage === 2 || currentPage === 3) {
          return {
            text: metadata.others.headerText,
            alignment: 'center',
            italics: true,
            style: 'pageHeader',
          };
        } else {
          return {};
        }
      },
      footer: function (currentPage) {
        if (currentPage > 1) {
          return {
            stack: [
              {
                image: images.FOOTER_LINE,
                width: 650,
                height: 1,
                margin: [50, 5, 0, 10],
              },
              {
                text: [
                  { text: metadata.others.footerText, italics: true },
                  { text: ' |   ' + currentPage },
                ],
                alignment: 'right',
                style: 'pageFooter',
              },
            ],
          };
        } else {
          return {};
        }
      },
      pageSize: 'LETTER',
      pageOrientation: 'landscape',
      pageMargins: [50, 50, 50, 45],
      defaultStyle: {
        fontSize: 7,
        columnGap: 10,
      },
      content: [
        {
          image: images.BACKGROUND,
          width: 1000,
          absolutePosition: { x: 0, y: 300 },
        },
        {
          image: images.ABSTRACT,
          width: 450,
        },
        {
          image: images[metadata.others.logoName],
          width: 400,
          absolutePosition: { x: 350, y: 180 },
        },
        {
          text: metadata.title,
          fontSize: 40,
          color: '#777',
          bold: true,
          absolutePosition: { x: 150, y: 450 },
          pageBreak: 'after',
        },

        {
          toc: {
            title: {
              text: ' In this vulnerability Report',
              style: 'tocTitle',
            },
            numberStyle: 'tocNumber',
          },
          margin: [60, 35, 20, 60],
          pageBreak: 'after',
        },

        {
          text: [
            {
              text: metadata.others.reportSummary,
              style: 'contentHeader',
              tocItem: true,
              tocStyle: {
                fontSize: 16,
                bold: true,
                color: '#4863A0',
                margin: [80, 15, 0, 60],
              },
            },
            {
              text: `    ${metadata.others.summaryRange}`,
              color: '#3090C7',
              fontSize: 10,
            },
          ],
        },

        {
          text: metadata.others.topImpactfulVulnerabilities,
          style: 'contentSubHeader',
          tocItem: true,
          tocStyle: {
            fontSize: 12,
            italic: true,
            color: 'black',
            margin: [95, 10, 0, 60],
          },
        },

        {
          columns: [
            {
              image: charts.canvas.topImpactfulVulnerabilies,
              width: 700,
            },
          ],
        },

        {
          text: metadata.others.topVulnerableImages,
          style: 'contentSubHeader',
          tocItem: true,
          tocStyle: {
            fontSize: 12,
            italic: true,
            color: 'black',
            margin: [95, 10, 0, 60],
          },
        },

        {
          columns: [
            {
              image: charts.canvas.topVulnerableImages,
              width: 700,
            },
          ],

          pageBreak: 'after',
        },

        {
          text: [
            {
              text: metadata.others.subTitleDetails,
              style: 'contentHeader',
              tocItem: true,
              tocStyle: {
                fontSize: 16,
                bold: true,
                color: '#4863A0',
                margin: [80, 15, 0, 60],
              },
            },
            {
              text: `    ${metadata.others.detailsLimit}`,
              color: '#fe6e6b',
              fontSize: 10,
            },
          ],
        },

        {
          style: 'tableExample',
          table: {
            headerRows: 1,
            dontBreakRows: false,
            widths: ['10%', '25%', '6%', '27%', '12%', '20%'],
            body: [
              [
                { text: metadata.header.name, style: 'tableHeader' },
                { text: metadata.header.desc, style: 'tableHeader' },
                { text: metadata.header.score, style: 'tableHeader' },
                { text: metadata.header.package, style: 'tableHeader' },
                {
                  text: metadata.header.publishedTime,
                  style: 'tableHeader',
                },
                { text: metadata.header.impact, style: 'tableHeader' },
              ],
            ],
          },
          pageBreak: 'after',
        },
        {
          text: [
            {
              text: metadata.others.appendixText,
              style: 'contentHeader',
              tocItem: true,
              tocStyle: {
                fontSize: 16,
                bold: true,
                color: '#4863A0',
                margin: [80, 15, 0, 60],
              },
            },
            {
              text: `    (${metadata.others.appendixDesc})`,
              color: '#3090C7',
              fontSize: 10,
            },
          ],
        },
        {},
        {
          text: [
            {
              text: metadata.others.appendixPackagesText,
              style: 'contentHeader',
              tocItem: true,
              tocStyle: {
                fontSize: 16,
                bold: true,
                color: '#4863A0',
                margin: [80, 15, 0, 60],
              },
            },
            {
              text: `    (${metadata.others.appendixPackagesDesc})`,
              color: '#3090C7',
              fontSize: 10,
            },
          ],
          pageBreak: 'before',
        },
        {},
        {
          text: '\n\n',
        },
      ],
      styles: {
        pageHeader: {
          fontSize: 14,
          italic: true,
          bold: true,
          color: 'grey',
          margin: [0, 10, 5, 5],
        },
        pageFooter: {
          fontSize: 12,
          color: 'grey',
          margin: [0, 5, 55, 5],
        },
        pageFooterImage: {
          width: 750,
          height: 1,
          margin: [50, 5, 10, 10],
        },
        tocTitle: {
          fontSize: 22,
          color: '#566D7E',
          lineHeight: 2,
        },
        tocNumber: {
          italics: true,
          fontSize: 15,
        },
        tableHeader: {
          bold: true,
          fontSize: 10,
          alignment: 'center',
        },
        contentHeader: {
          fontSize: 16,
          bold: true,
          color: '#3090C7',
          margin: [0, 10, 0, 10],
        },
        contentSubHeader: {
          fontSize: 14,
          bold: true,
          color: 'black',
          margin: [0, 10, 0, 10],
        },
        content: {
          fontSize: 10,
          margin: [5, 5, 5, 5],
        },
        title: {
          bold: true,
          fontSize: 8,
        },
        subTitle: {
          bold: true,
          fontSize: 7,
        },
        appendixTitle: {
          fontSize: 10,
          bold: true,
          margin: [0, 2, 0, 2],
        },
        appendixText: {
          fontSize: 8,
          margin: [0, 2, 0, 2],
        },
        danger: {
          bold: true,
          color: '#dc4034',
          fontSize: 8,
        },
        warning: {
          bold: true,
          color: '#ff9800',
          fontSize: 8,
        },
        info: {
          bold: true,
          color: '#2196f3',
          fontSize: 8,
        },
        error: {
          bold: true,
          color: '#e91e63',
          fontSize: 8,
        },
      },
    };

    let index = 1;

    for (let item of docData.data as any) {
      docDefinition.content[11].table.body.push(
        this._getRowData(item, index, metadata)
      );
      index++;
    }
    let appendix = this.prepareAppendix(docData, metadata);
    docDefinition.content[13] = appendix.appendix;
    docDefinition.content[15] = appendix.appendix4Packages;
    console.log('docDefinition', docDefinition);

    return docDefinition;
  }

  prepareNamesWith3Columns = function (names, type) {
    let namesMatrix: any = [];
    let rowData: any = [];
    for (let i = 0; i < names.length; i++) {
      if (i % 3 === 0) {
        if (Math.floor(i / 3) > 0)
          namesMatrix.push({ columns: JSON.parse(JSON.stringify(rowData)) });
        rowData = [{ text: '' }, { text: '' }, { text: '' }];
      }
      rowData[i % 3] = {
        text: names[i].display_name,
        color: MapConstant.PDF_TEXT_COLOR[type.toUpperCase()],
        style: 'appendixText',
      };
    }
    namesMatrix.push({ columns: JSON.parse(JSON.stringify(rowData)) });
    return namesMatrix;
  };

  preparePackagesWith3Columns(item, metaData) {
    let packagesMatrix: any = [];
    let rowData: any = [];
    let packageGrids = this._getPackage(item, metaData, true);
    for (let i = 0; i < packageGrids.length; i++) {
      if (i % 3 === 0) {
        if (Math.floor(i / 3) > 0)
          packagesMatrix.push({ columns: JSON.parse(JSON.stringify(rowData)) });
        rowData = [{}, {}, {}];
      }
      rowData[i % 3] = packageGrids[i];
    }
    packagesMatrix.push({ columns: JSON.parse(JSON.stringify(rowData)) });
    return packagesMatrix;
  }

  prepareAppendix(docData, metaData) {
    let appendix: any = [];
    let appendix4Packages: any = [];
    console.log('docData.data: ', docData.data);
    docData.data.forEach(item => {
      let cve = {
        text: item.name,
        style: 'appendixTitle',
      };
      let image = {
        text: `${docData.metadata.data.images}: ${item.images.length}`,
        color: MapConstant.PDF_TEXT_COLOR.IMAGE,
        style: 'appendixTitle',
      };
      let imageList = this.prepareNamesWith3Columns(item.images, 'image');
      let container = {
        text: `${docData.metadata.data.containers}: ${item.workloads.length}`,
        color: MapConstant.PDF_TEXT_COLOR.CONTAINER,
        style: 'appendixTitle',
      };
      let containerList = this.prepareNamesWith3Columns(
        item.workloads,
        'container'
      );
      let node = {
        text: `${docData.metadata.data.nodes}: ${item.nodes.length}`,
        color: MapConstant.PDF_TEXT_COLOR.NODE,
        style: 'appendixTitle',
      };
      let nodeList = this.prepareNamesWith3Columns(item.nodes, 'node');
      let platform = {
        text: `${docData.metadata.data.platforms}: ${item.platforms.length}`,
        color: MapConstant.PDF_TEXT_COLOR.PLATFORM,
        style: 'appendixTitle',
      };
      let platformList = this.prepareNamesWith3Columns(
        item.platforms,
        'platform'
      );

      let packagesRaw = Object.entries(item.packages);
      let packageCount = packagesRaw.length;
      let packages = this.preparePackagesWith3Columns(item, metaData);
      let lineBreak = {
        text: '\n\n',
      };
      appendix.push(cve);
      appendix4Packages.push(cve);
      if (item.images.length > 0) {
        appendix.push(image);
        appendix = appendix.concat(imageList);
      }
      if (item.workloads.length > 0) {
        appendix.push(container);
        appendix = appendix.concat(containerList);
      }
      if (item.nodes.length > 0) {
        appendix.push(node);
        appendix = appendix.concat(nodeList);
      }
      if (item.platforms.length > 0) {
        appendix.push(platform);
        appendix = appendix.concat(platformList);
      }
      if (packageCount > 0) {
        appendix4Packages.push(packages);
      }
      appendix.push(lineBreak);
      appendix4Packages.push(lineBreak);
    });
    console.log('appendix: ', appendix, appendix4Packages);
    return {
      appendix,
      appendix4Packages,
    };
  }

  _getRowData(item, id, metadata) {
    let name = item.name;
    let description = item.description;
    let score = this._getScore(item, metadata);
    let _packages = this._getPackage(item, metadata, false);
    let publishedTime = this._getPublishedTime(item);
    let impact = this._getImpact(item, metadata);

    return [name, description, score, _packages, publishedTime, impact];
  }

  _getPublishedTime(item) {
    return this.datePipe.transform(
      item.published_timestamp * 1000,
      'MMM dd, y HH:mm:ss'
    );
  }

  _getScore(item, metadata) {
    let scoreList: any = {};
    let scoreStyle = '';
    if (
      item.severity.toLowerCase() === 'high' ||
      item.severity.toLowerCase() === 'critical'
    ) {
      scoreStyle = 'danger';
    } else if (
      item.severity.toLowerCase() === 'medium' ||
      item.severity.toLowerCase() === 'warning'
    ) {
      scoreStyle = 'warning';
    } else {
      scoreStyle = 'info';
    }
    scoreList.ul = [
      { text: `${metadata.data.v2}: ${item.score}`, style: scoreStyle },
      { text: `${metadata.data.v3}: ${item.score_v3}`, style: scoreStyle },
    ];
    return scoreList;
  }

  _getPackage(item, metadata, isFullList) {
    let details: any = [];
    let packages = Object.entries(item.packages);
    let originalPackageCount = packages.length;
    if (!isFullList) {
      packages = packages.slice(0, 3);
    }
    packages.forEach(([k, v]) => {
      let packageName = {};
      if (k) {
        packageName = {
          text: [k],
        };
      }

      let version;
      let impactedVersion: any = {};
      impactedVersion.ul = [];
      let fixedVersion: any = {};
      fixedVersion.ul = [];

      if (v && Array.isArray(v)) {
        v.forEach(package_version => {
          impactedVersion.ul.push(package_version.package_version);
          fixedVersion.ul.push(
            package_version.fixed_version
              ? package_version.fixed_version
              : 'N/A'
          );
        });
      }
      version = {
        table: {
          widths: ['50%', '50%'],
          body: [
            [
              { text: metadata.data.impactedVersion },
              { text: metadata.data.fixedVersion },
            ],
            [impactedVersion, fixedVersion],
          ],
        },
        layout: {
          hLineColor: 'gray',
          vLineColor: 'gray',
        },
      };
      details.push([packageName, version]);
    });
    if (originalPackageCount > 3 && !isFullList) {
      details.push([{ text: `...... (${originalPackageCount} Packages)` }]);
    }
    return details;
  }

  _getImpact(item, metadata) {
    let impactList: any = [];
    let platformList: any = {};
    platformList.ul = [];
    let imageList: any = {};
    imageList.ul = [];
    let nodeList: any = {};
    nodeList.ul = [];
    let containerList: any = {};
    containerList.ul = [];
    if (item.platforms && item.platforms.length > 0) {
      platformList.ul = item.platforms.map(platform => platform.display_name);
      impactList.push({ text: `${metadata.data.platforms}`, bold: true });
      if (platformList.ul.length > 5) {
        let omitedList = platformList.ul.slice(0, 5);
        omitedList.push(
          `......(${platformList.ul.length} ${metadata.data.platforms})`
        );
        impactList.push({ ul: omitedList });
        impactList.push(' ');
      } else {
        impactList.push(platformList);
        impactList.push(' ');
      }
    }
    if (item.images && item.images.length > 0) {
      imageList.ul = item.images.map(image => image.display_name);
      impactList.push({ text: `${metadata.data.images}`, bold: true });
      if (imageList.ul.length > 5) {
        let omitedList = imageList.ul.slice(0, 5);
        omitedList.push(
          `......(${imageList.ul.length} ${metadata.data.images})`
        );
        impactList.push({ ul: omitedList });
        impactList.push(' ');
      } else {
        impactList.push(imageList);
        impactList.push(' ');
      }
    }
    if (item.nodes && item.nodes.length > 0) {
      nodeList.ul = item.nodes.map(node => node.display_name);
      impactList.push({ text: `${metadata.data.nodes}`, bold: true });
      if (nodeList.ul.length > 5) {
        let omitedList = nodeList.ul.slice(0, 5);
        omitedList.push(`......(${nodeList.ul.length} ${metadata.data.nodes})`);
        impactList.push({ ul: omitedList });
        impactList.push(' ');
      } else {
        impactList.push(nodeList);
        impactList.push(' ');
      }
    }
    if (item.workloads && item.workloads.length > 0) {
      containerList.ul = item.workloads.map(workload => workload.display_name);
      impactList.push({ text: `${metadata.data.containers}`, bold: true });
      if (containerList.ul.length > 5) {
        let omitedList = containerList.ul.slice(0, 5);
        omitedList.push(
          `......(${containerList.ul.length} ${metadata.data.containers})`
        );
        impactList.push({ ul: omitedList });
      } else {
        impactList.push(containerList);
      }
    }
    return impactList;
  }

  private drawReport(docDefinition) {
    let report = pdfMake.createPdf(docDefinition);
    report.getBlob(blob => {
      saveAs.saveAs(
        blob,
        `${this.translateService.instant(
          'audit.report.reportTitle'
        )}_${this.utilsService.parseDatetimeStr(new Date())}.pdf`
      );
    });
  }

  private getChartsForPDF() {
    let topImpactfulVulnerabilies = (
      document?.getElementById('vulnNodesBarPDF') as HTMLCanvasElement
    ).toDataURL();
    let topVulnerableImages = (
      document.getElementById('vulnImagesBarPDF') as HTMLCanvasElement
    ).toDataURL();

    return {
      canvas: {
        topImpactfulVulnerabilies,
        topVulnerableImages,
      },
    };
  }
}
